package com.xuecheng.media.service.jobhandler;

import com.xuecheng.base.utils.Mp4VideoUtil;
import com.xuecheng.media.model.po.MediaProcess;
import com.xuecheng.media.service.MediaFileProcessService;
import com.xuecheng.media.service.MediaFileService;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * XxlJob开发示例（Bean模式）
 * <p>
 * 开发步骤：
 * 1、任务开发：在Spring Bean实例中，开发Job方法；
 * 2、注解配置：为Job方法添加注解 "@XxlJob(value="自定义jobhandler名称", init = "JobHandler初始化方法", destroy = "JobHandler销毁方法")"，注解value值对应的是调度中心新建任务的JobHandler属性的值。
 * 3、执行日志：需要通过 "XxlJobHelper.log" 打印执行日志；
 * 4、任务结果：默认任务结果为 "成功" 状态，不需要主动设置；如有诉求，比如设置任务结果为失败，可以通过 "XxlJobHelper.handleFail/handleSuccess" 自主设置任务结果；
 * todo 将来执行的任务写在里面
 *
 * @author xuxueli 2019-12-11 21:52:51
 */
@Component
@Slf4j
public class VideoTask {

    @Resource
    MediaFileProcessService mediaFileProcessService;
    @Resource
    MediaFileService mediaFileService;
    @Value("${videoprocess.ffmpegpath}")
    private String ffmpegpath;

    @XxlJob("videoJobHandler")
    public void videoJobHandler() throws Exception {
        // 分片参数
        int shardIndex = XxlJobHelper.getShardIndex();//执行器的序号,从零开始
        int shardTotal = XxlJobHelper.getShardTotal();//执行器的个数
        int processors = Runtime.getRuntime().availableProcessors();//确定cpu核心数
//        领取任务
        List<MediaProcess> mediaProcessList = mediaFileProcessService.getMediaProcessList(shardIndex, shardTotal, processors);
        int size = mediaProcessList.size();//任务数量
        log.debug("视频处理任务数:{}", size);
        if (size <= 0) {
            return;
        }
//        todo 主要任务有线程池解决,那么当开启完线程池后就开始异步,这个执行器就完成了,此时处于空闲状态,但是线程池中的任务完成肯定较慢,如果执行器在接收新任务,可能会导致线程爆满
        ExecutorService executorService = Executors.newFixedThreadPool(size);//线程池,,应该保证线程池数量大小不超过CPU
//       todo  创建一个计数器保证执行器与所开的线程同步结束,计数器要放在末尾,保证该线程结束
        CountDownLatch countDownLatch = new CountDownLatch(size);

        mediaProcessList.forEach(mediaProcess -> {
            executorService.execute(() -> {//将每一个任务都开启一个线程来解决
                try{//一定要包在遍历里面,因为在外面,里面一出错整个线程池就结束了
                    Long taskId = mediaProcess.getId();//任务ID
                    String fileId = mediaProcess.getFileId();//md5值,唯一标识
                    //抢任务
                    boolean b = mediaFileProcessService.startTask(taskId);
                    if (!b) {
                        log.debug("抢占任务失败id:{}", taskId);
                        return;
                    }
                    String bucket = mediaProcess.getBucket();
                    String objectName = mediaProcess.getFilePath();
//                 下载minio视频到本地,因为后续要进行转码
                    File file = mediaFileService.downloadFileFromMinIO(bucket, objectName);
                    if (file == null) {
                        log.debug("下载视频出错,任务id:{},bucket:{},objectName:{}", taskId, bucket, objectName);
//                    保存任务失败结果
                        mediaFileProcessService.saveProcessFinishStatus(taskId, "3", fileId, null, "下载视频到本地失败");
                        return;
                    }
//                源avi视频的路径
                    String video_path = file.getAbsolutePath();
                    String mp4_name = fileId + ".mp4";//转化后mp4的名称
                    File mp4File = null;// 这个临时文件是为了接收转码后的mp4文件 todo 不能在下载文件的基础上进行修改吗
                    try {
                        mp4File = File.createTempFile("minio", ".mp4");
                    } catch (IOException e) {
                        log.debug("创建临时文件异常,{}", e.getMessage());
//                    保存任务失败的的结果
                        mediaFileProcessService.saveProcessFinishStatus(taskId, "3", fileId, null, "创建临时文件异常");
                        return;
                    }
                    String mp4_path = mp4File.getAbsolutePath();
//                开始转化
                    Mp4VideoUtil videoUtil = new Mp4VideoUtil(ffmpegpath, video_path, mp4_name, mp4_path);
                    String result = videoUtil.generateMp4();
                    if (!"success".equals(result)) {
                        log.debug("视频转码失败原因:{},bucket:{},objectName:{}", result, bucket, objectName);
//                    保存成功信息
                        mediaFileProcessService.saveProcessFinishStatus(taskId, "3", fileId, null, result);
                        return;
                    }
//                上传至minio
                    String url = getFilePathByMd5(fileId, ".mp4");
//                 更新成功位置
                    boolean b1 = mediaFileService.addMediaFilesToMinIO(mp4File.getAbsolutePath(), "video/mp4", bucket, url);
                    if (!b1) {
                        log.debug("上传mp4到minio失败,taskId:{}", taskId);
                        mediaFileProcessService.saveProcessFinishStatus(taskId, "3", fileId, null, "上传mp4到minio失败");
                        return;
                    }
//                mp4文件在minio的位置

                    mediaFileProcessService.saveProcessFinishStatus(taskId, "2", fileId, url, "创建临时文件异常");
                } finally {//保证出现了异常也要减一
                    countDownLatch.countDown();
                }
//        todo  那视频没有转码成功咋办??转码不用分片转码吗???
            });
        });
//        todo 指定阻塞的的最大等待时间
        countDownLatch.await(30, TimeUnit.MINUTES);

    }

    private String getFilePathByMd5(String fileMd5, String fileExt) {
        return fileMd5.substring(0, 1) + "/" + fileMd5.substring(1, 2) + "/" + fileMd5 + "/" + fileMd5 + fileExt;
    }
}
